﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;

using SMBL.ConfigElement.FileLocate;
using SMBL.Global;

namespace SMBL
{
    namespace Core
    {
        /// <summary>
        /// The mail error system
        /// @Author: Push
        /// </summary>
        public class ErrorSystem
        {
            //////////////////////////////////////////////////////////////////////////
            // Private Parameters
            //////////////////////////////////////////////////////////////////////////
            // Used for a single instance
            // the date time the error occurred
            private DateTime occurredDate;
            // The error Message
            private String errorMessage;
            // The Method which lead to the error
            private String occurredMethod;
            // The Id of the error
            private Int64 errorId;

            //////////////////////////////////////////////////////////////////////////
            // Static Parameters
            // For the System Usage
            //////////////////////////////////////////////////////////////////////////
            // For Synchronization
            private static Boolean SynPool;
            private static Boolean SynFile;

            // Error Pool
            private static ArrayList ErrorPool;
            // System Information
            //private static Int32 gMaxCacheSize;
            private static Int32 gCacheSize;
            //private static Int32 gMaxFileSize;
            private static FileLocation gErrorFileLocation;

            // Increased Error Id
            private static Int64 gLastErrorId;

            /// <summary>
            /// Create an Error Unit via the Exception
            /// @Author: Push
            /// </summary>
            /// <param name="exp"></param>
            private ErrorSystem(Exception exp)
            {
                occurredDate = DateTime.Now;
                errorMessage = exp.Message;
                occurredMethod = exp.TargetSite.Name;
                errorId = ++gLastErrorId;
            }

            /// <summary>
            /// Create an Error Unit via the message sent in
            /// @Author: Push
            /// </summary>
            /// <param name="ErrorMessage"></param>
            /// <param name="OccurredMethod"></param>
            private ErrorSystem(String ErrorMessage, String OccurredMethod)
            {
                occurredDate = DateTime.Now;
                errorMessage = ErrorMessage;
                occurredMethod = OccurredMethod;
                errorId = ++gLastErrorId;
            }

            //////////////////////////////////////////////////////////////////////////
            // System API
            //////////////////////////////////////////////////////////////////////////

            /// <summary>
            /// Initialize the error system
            /// 1. Check the error file
            /// 2. Initialize the error pool
            /// 3. Initialize Global Parameters
            /// @Author: Push
            /// </summary>
            /// <param name="ErrorFileLocation"></param>
            public static void Initialize(FileLocation ErrorFileLocation)
            {
                // Check Error File
                gErrorFileLocation = ErrorFileLocation;
                FileInfo errorFileInfo = new FileInfo(gErrorFileLocation.Path);
                if (!errorFileInfo.Exists)
                {
                    try
                    {
                        StreamWriter errorFileStream = new StreamWriter(errorFileInfo.Create());
                        errorFileStream.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
                        errorFileStream.WriteLine("<!DOCTYPE Error SYSTEM 'Error.dtd'>");
                        errorFileStream.WriteLine("<Error></Error>");
                        errorFileStream.Close();
                    }
                    catch (System.Exception e)
                    {
                        // Catch the UnHandled exception
                        BasicErrorSystem.UnhandledError(e);
                        // failed to initialize the error system
                        return;
                    }
                }

                // Check file size
                if (errorFileInfo.Length / 1024 > gErrorFileLocation.MaxSize)
                    CleanFile();
                // Initialize the Error Pool
                ErrorPool = new ArrayList();
                gCacheSize = 0;
                //gMaxCacheSize = ErrorLocate.CacheSize;
                gLastErrorId = 0;
                SynPool = false;
                SynFile = false;
            }

            /// <summary>
            /// Write all errors to the file
            /// Release the resources
            /// @Author: Push
            /// </summary>
            public static void Terminate()
            {
                try
                {
                    // Write error
                    StreamToFile();
                }
                catch (Exception e)
                {
                    // UnHandled Exception
                    BasicErrorSystem.UnhandledError(e);
                }
            }

            /// <summary>
            /// Clean Error File
            /// Back up the old one, then create a new one
            /// </summary>
            private static void CleanFile()
            {
                // Lock
                while (SynFile) ;
                SynFile = true;

                // Backup old file
                //FileLocate ErrorLocate = FileLocate.GetLocate("Error");
                FileInfo errorFileInfo = new FileInfo(gErrorFileLocation.Path);
                try
                {
                    // Delete Old One
                    errorFileInfo.MoveTo(gErrorFileLocation.Path + "." + 
                        DateTime.Now.Year.ToString() + "." + DateTime.Now.Month.ToString() + "." +
                        DateTime.Now.Day.ToString() + "." + DateTime.Now.Hour.ToString() + "." +
                        DateTime.Now.Minute.ToString() + "." + DateTime.Now.Second.ToString() + ".backup");
                    errorFileInfo = new FileInfo(gErrorFileLocation.Path);
                    errorFileInfo.Delete();

                    // Create new file
                    //errorFile.Create();
                    StreamWriter streamErrorFile = new StreamWriter(errorFileInfo.Create());
                    streamErrorFile.WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
                    streamErrorFile.WriteLine("<!DOCTYPE Error SYSTEM 'Error.dtd'>");
                    streamErrorFile.WriteLine("<Error></Error>");
                    streamErrorFile.Close();
                }
                catch (System.Exception e)
                {
                    // IO Exception
                    BasicErrorSystem.UnhandledError(e);
                }

                // UnLock
                SynFile = false;
            }

            /// <summary>
            /// Catch an Error and add to the error pool
            /// @Author: Push
            /// </summary>
            /// <param name="e"></param>
            /// <returns>The Error Id of the exception</returns>
            public static long CatchError(Exception e)
            {
                // Check available
                while (SynPool) ;
                // New error
                ErrorSystem err = new ErrorSystem(e);
                long EID = err.errorId;
                ErrorPool.Add(err);

                gCacheSize += e.ToString().Length;
                // Check the Pool size
                CheckPoolSize();
                return EID;
            }

            /// <summary>
            /// Catch an Error and add to the error pool
            /// @Author: Push
            /// </summary>
            /// <param name="ErrorMessage"></param>
            /// <param name="OccurredMethod"></param>
            /// <returns>The Error Id of the error unit</returns>
            public static long CatchError(String ErrorMessage, String OccurredMethod)
            {
                // Check available
                while (SynPool) ;
                // New error
                ErrorSystem err = new ErrorSystem(ErrorMessage, OccurredMethod);
                long EID = err.errorId;
                ErrorPool.Add(err);

                gCacheSize += err.ToString().Length;
                // Check the Pool size
                CheckPoolSize();
                return EID;
            }

            /// <summary>
            /// Check current error pool's size
            /// Write all error to file or send report mail
            /// @Author: Push
            /// </summary>
            private static void CheckPoolSize()
            {
                // Check the Pool Size
                if (gCacheSize / 1024 >= gErrorFileLocation.MaxCacheSize)
                {
                    try
                    {
                        // Write the pool to the error file
                        StreamToFile();
                    }
                    catch (Exception exp)
                    {
                        // UnHandled IO Exception
                        BasicErrorSystem.UnhandledError(exp);
                        // Send current error pool
                        BasicErrorSystem.UnhandledError(ErrorPool);
                    }
                    // Clean the Pool;
                    ErrorPool.Clear();
                }
            }

            /// <summary>
            /// Write error pool to the error file
            /// The method does not need to catch the exceptions
            /// because all method invoke this one will catch them.
            /// @Author: Push
            /// </summary>
            private static void StreamToFile()
            {
                // Lock
                while (SynFile) ;
                SynFile = true;
                SynPool = true;

                //FileLocate ErrorLocate = FileLocate.GetLocate("Error");
                System.Xml.XmlDocument ErrorXml = new System.Xml.XmlDocument();
                //ErrorXml.Load(ErrorLocate.Path);
                ErrorXml.Load(gErrorFileLocation.Path);
                for (int i = ErrorPool.Count - 1; i >= 0; --i)
                {
                    // Get the error and generate the xml elements
                    ErrorSystem tmpError = (ErrorSystem)ErrorPool[i];
                    System.Xml.XmlElement ErrorItem = ErrorXml.CreateElement("ErrorItem");
                    ErrorItem.SetAttribute("EID", tmpError.errorId.ToString());
                    System.Xml.XmlElement Date = ErrorXml.CreateElement("Date");
                    Date.InnerText = tmpError.occurredDate.ToString();
                    System.Xml.XmlElement Message = ErrorXml.CreateElement("Message");
                    Message.InnerText = tmpError.errorMessage;
                    System.Xml.XmlElement TargetSite = ErrorXml.CreateElement("TargetSite");
                    TargetSite.InnerText = tmpError.occurredMethod;

                    // combine the exception
                    ErrorItem.AppendChild(Date);
                    ErrorItem.AppendChild(Message);
                    ErrorItem.AppendChild(TargetSite);

                    // Add to the file
                    ErrorXml.DocumentElement.AppendChild(ErrorItem);
                }

                // Save the XML file
                ErrorXml.Save(gErrorFileLocation.Path);

                // Unlock
                SynPool = false;
                SynFile = false;

                // Check File size
                FileInfo errorFileInfo = new FileInfo(gErrorFileLocation.Path);
                if (errorFileInfo.Length / 1024 >= gErrorFileLocation.MaxSize)
                    CleanFile();
            }

            /// <summary>
            /// Get the error message
            /// @Author: Push
            /// </summary>
            /// <param name="EID">The Id of the error to query</param>
            /// <returns>The error Message</returns>
            public static String GetError(long EID)
            {
                // Check the error id range
                if (EID < 0) return "Error ID Out Of Range, no error message returned.";
                if (EID >= ErrorPool.Count) return "Error ID Out Of Range, no error message returned.";
                // Check if still in the Pool
                Int32 delta = (Int32)(EID - ((ErrorSystem)ErrorPool[0]).errorId);
                if (delta >= 0)
                    return ((ErrorSystem)ErrorPool[delta]).errorMessage;

                // do not search the file cause the error happened long time ago
                // the user should open the error page to see the detail of the error
                return "Too much time passed after the error happened. Open the error file to see the details";
            }

            //////////////////////////////////////////////////////////////////////////
            // Method
            //////////////////////////////////////////////////////////////////////////

            /// <summary>
            /// To String
            /// Used by the ErrorReport
            /// @Author: Push
            /// </summary>
            /// <returns>The Error String</returns>
            public override string ToString()
            {
                //return base.ToString();
                return this.occurredDate.ToString() + ": " +
                    this.errorMessage + "<br />" +
                    this.occurredMethod + "<br />";
            }
        }
    }
}